/**
 *
 */
package com.wedroid.r2d2.contact;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.cache.ehcache.EhCacheCacheManager;
import org.springframework.util.Assert;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import net.sf.ehcache.config.CacheConfiguration;
import net.sf.ehcache.config.PersistenceConfiguration;
import net.sf.ehcache.config.PersistenceConfiguration.Strategy;
import net.sf.ehcache.store.MemoryStoreEvictionPolicy;

/**
 * @author lute
 */
public class EhCacheContactStore implements ContactStore, InitializingBean {

    private static final Logger logger = LoggerFactory.getLogger(EhCacheContactStore.class);

    private CacheManager cacheManager;

    private int maxCacheSize = 0;

    private static final String CONTACT = "contact";
    private static final String FRIEND = "friend";
    private static final String OFFICIAL = "official";
    private static final String SPECIAL = "special";
    private static final String GROUP = "group";
    private static final String GROUP_MEMBER = "group_member";

    private Cache contactCache;
    private Cache friendCache;
    private Cache officialCache;
    private Cache specialCache;
    private Cache groupCache;
    private Cache groupMemberCache;

    /**
     *
     */
    public EhCacheContactStore() {
    }

    /**
     * @param maxCacheSize the maxCacheSize to set
     */
    public void setMaxCacheSize(int maxCacheSize) {
        if (maxCacheSize < 0) {
            throw new IllegalArgumentException("Maximum cache size can not be less than 0.");
        }
        this.maxCacheSize = maxCacheSize;
    }

    /**
     * @param ehCacheCacheManager the ehCacheCacheManager to set
     */
    public void setEhCacheCacheManager(EhCacheCacheManager ehCacheCacheManager) {
        this.cacheManager = ehCacheCacheManager.getCacheManager();
    }

    /* (non-Javadoc)
     * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
     */
    public void afterPropertiesSet() throws Exception {
        Assert.notNull(cacheManager, "EhCache is not configured properly.");

        contactCache = createCache(CONTACT);
        friendCache = createCache(FRIEND);
        officialCache = createCache(OFFICIAL);
        specialCache = createCache(SPECIAL);
        groupCache = createCache(GROUP);
        groupMemberCache = createCache(GROUP_MEMBER);
    }

    /**
     * @param cacheName
     * @return
     */
    private Cache createCache(String cacheName) {
        Cache cache = cacheManager.getCache(cacheName);
        if (cache != null) {
            logger.info("#createCache - Detect cache [{}] has been initialized, therefore, use it as default.", cacheName);
            return cache;
        }

        CacheConfiguration configuration = new CacheConfiguration(cacheName, maxCacheSize)
                .memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.LFU)
                .eternal(true)
                .persistence(new PersistenceConfiguration().strategy(Strategy.NONE));
        cache = new Cache(configuration);
        cacheManager.addCache(cache);
        return cache;
    }

    /* (non-Javadoc)
     * @see com.wedroid.r2d2.contact.ContactStore#storeFriend(com.wedroid.r2d2.contact.Contact)
     */
    public void storeFriend(Contact contact) {
        Element element = new Element(contact.getUserName(), contact);
        contactCache.put(element);
        friendCache.put(element);
    }

    /* (non-Javadoc)
     * @see com.wedroid.r2d2.contact.ContactStore#storeOfficial(com.wedroid.r2d2.contact.Contact)
     */
    public void storeOfficial(Contact official) {
        Element element = new Element(official.getUserName(), official);
        contactCache.put(element);
        officialCache.put(element);
    }

    /* (non-Javadoc)
     * @see com.wedroid.r2d2.contact.ContactStore#storeGroup(com.wedroid.r2d2.contact.Contact)
     */
    public void storeGroup(Contact group) {
        Element element = new Element(group.getUserName(), group);
        contactCache.put(element);
        groupCache.put(element);
    }

    /* (non-Javadoc)
     * @see com.wedroid.r2d2.contact.ContactStore#storeSpecial(com.wedroid.r2d2.contact.Contact)
     */
    public void storeSpecial(Contact special) {
        Element element = new Element(special.getUserName(), special);
        contactCache.put(element);
        specialCache.put(element);
    }

    /* (non-Javadoc)
     * @see com.wedroid.r2d2.contact.ContactStore#storeGroupMember(java.lang.String, com.wedroid.r2d2.contact.Contact)
     */
    @SuppressWarnings("unchecked")
    public void storeGroupMember(String groupName, Contact contact) {
        if (groupMemberCache.get(groupName) == null) {
            synchronized (groupMemberCache) {
                if (groupMemberCache.get(groupName) == null) {
                    groupMemberCache.put(new Element(groupName, new HashMap<String, Contact>()));
                }
            }
        }
        Map<String, Contact> groupMembers = (Map<String, Contact>) groupMemberCache.get(groupName).getObjectValue();
        groupMembers.put(contact.getUserName(), contact);
    }

    /* (non-Javadoc)
     * @see com.wedroid.r2d2.contact.ContactStore#getContact(java.lang.String)
     */
    public Contact getContact(String username) {
        return getCacheValue(contactCache, username, Contact.class);
    }

    /* (non-Javadoc)
     * @see com.wedroid.r2d2.contact.ContactStore#getGroupMember(java.lang.String, java.lang.String)
     */
    @SuppressWarnings("unchecked")
    public Contact getGroupMember(String groupName, String username) {
        Map<String, Contact> members = getCacheValue(groupMemberCache, groupName, Map.class);
        if (members != null) {
            return members.get(username);
        }
        return null;
    }

    /* (non-Javadoc)
     * @see com.wedroid.r2d2.contact.ContactStore#getAllGroups()
     */
    public List<Contact> getAllGroups() {
        List<?> keys = groupCache.getKeys();
        List<Contact> groups = new ArrayList<>();
        for (Object key : keys) {
            groups.add(getCacheValue(groupCache, key, Contact.class));
        }
        return groups;
    }

    /* (non-Javadoc)
     * @see com.wedroid.r2d2.contact.ContactStore#getAllGroupMembers()
     */
    @SuppressWarnings("unchecked")
    public Map<String, List<Contact>> getAllGroupMembers() {
        List<String> keys = groupMemberCache.getKeys();
        Map<String, List<Contact>> result = new HashMap<>();
        for (String key : keys) {
            Map<String, Contact> element = getCacheValue(groupMemberCache, key, Map.class);
            List<Contact> groups = new ArrayList<>();
            for (String memberName : element.keySet()) {
                groups.add(element.get(memberName));
            }
            result.put(key, groups);
        }
        return result;
    }

    private <T> T getCacheValue(Cache cache, Object key, Class<T> type) {
        Element element = cache.get(key);
        if (element != null) {
            Object object = element.getObjectValue();
            if (type.isInstance(object)) {
                return type.cast(object);
            }
        }
        return null;
    }

}
